﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Windows;
using NetPublisher.Properties;

namespace NetPublisher
{
    class AutoPublisher
    {
        public event EventHandler PublishFinished;

        public event EventHandler<ProcessDoneEventArgs> PublishProcess;

        private bool? _isWorking;

        /// <summary>
        /// Gets the value that indicates that publish in process.
        /// </summary>
        public bool IsWorking { get { return _isWorking.GetValueOrDefault(); } }

        public bool Paused { get; set; }

    	private readonly PublishFileParser _fileParser = PublishFileParser.Create();

        public PublishFileParser FileParser
    	{
    		get { return _fileParser; }
    	}

        private static readonly object CurrentPathLockObject = new object();
        
        private string _currentPath;
        /// <summary>
        /// Gets the path or currently uploading file.
        /// </summary>
        public string CurrentPath
        {
            get
            {
                lock(CurrentPathLockObject)
                {
                    return _currentPath;
                }
            }
            private set
            {
                lock(CurrentPathLockObject)
                {
                    _currentPath = value;
                }
            }
        }
        
        private double _currentProgress;
        /// <summary>
        /// Gets the path or currently uploading file.
        /// </summary>
        public double CurrentProgressPercents
        {
            get
            {
                lock(CurrentPathLockObject)
                {
                    return _currentProgress;
                }
            }
            private set
            {
                lock(CurrentPathLockObject)
                {
                    _currentProgress = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether to do quick publish. It ignores all files that starts from '#' on "PublishFiles.txt" settings file.
        /// </summary>
        public bool QuickPublish { get; set; }

        public void Publish()
        {
            ThreadPool.QueueUserWorkItem(PublishAsync);
        }

        /// <summary>
        /// Stop current publish process. It not pause it. The OnPublishFinished Event will be called.
        /// </summary>
        public void Stop()
        {
            _isWorking = false;
        }

        public void OnPublishFinished(EventArgs e)
        {
            if (PublishFinished != null) PublishFinished(this, e);
        }

        public void OnPublishProcess(ProcessDoneEventArgs e)
        {
            CurrentPath = e.FilePath;
            CurrentProgressPercents = e.Percents;
            if (PublishProcess != null) PublishProcess(this, e);
        }

        private void PublishAsync(object state)
        {
            try
            {
            	foreach (var ftpServer in FileParser.FtpServers)
            	{
            		PublishTo(state, ftpServer);
            	}
            }
            finally
            {
                OnPublishFinished(new EventArgs());
            }
        }

    	private void PublishTo(object state, FtpConnectionString ftpServer)
    	{
            if (!ftpServer.IsValid && AskToUpdateFtpSettings(null))
            {
				Process.Start(Application.ResourceAssembly.Location);
				Application.Current.Shutdown();
				return;
            }
			if (!ftpServer.IsSelected)
				return;
            FileParser.Parse(QuickPublish, FileParser.FtpServers.IndexOf(ftpServer));

    		using (var ftpClient = new FtpClient(ftpServer.Server, ftpServer.UserName, ftpServer.Password))
    		{
    			if (ftpServer.Port.HasValue)
    				ftpClient.Port = ftpServer.Port.Value;
    		    try
    		    {
    		        ftpClient.Login();
    		        if (!string.IsNullOrWhiteSpace(ftpServer.Path))
    		            ftpClient.ChangeDir(ftpServer.Path);
    		    }
    		    catch (SocketException ex)
    		    {
    				if (AskToUpdateFtpSettings(ex.ToStringWithInnerException()))
    				{
    					PublishAsync(state);
    					return;
    				}
    		    }
    			catch (FtpClient.FtpException ex)
    			{
    				if (AskToUpdateFtpSettings(ex.ToStringWithInnerException()))
    				{
    					PublishAsync(state);
    					return;
    				}
    			}
    			UploadFiles(FileParser, ftpClient);
    			FileParser.SavePrevFiles(FileParser.FtpServers.IndexOf(ftpServer));
    		}
    	}

    	private void UploadFiles(PublishFileParser fileParser, FtpClient ftpClient)
        {
            _isWorking = true;
            long totalLength = 0;
            //ftpClient.FileUploadingProcess += (sender, args) => OnPublishProcess(new ProcessDoneEventArgs((i+args.Percents)*100/FileParser.Files.Count, args.FilePath.Substring(FileParser.SolutionFolder.Length)));;
            ftpClient.FileUploadingProcess += (sender, args) => OnPublishProcess(new ProcessDoneEventArgs(((totalLength += args.Progress)/(double)fileParser.TotalFilesLength)*100, args.FilePath.Substring(fileParser.SolutionFolder.Length)));;
            var directories = new List<string>();
            string lastDirectory = null;
            foreach (var file in fileParser.Files)
            {
                if (_isWorking == false)
                    return;
                while (Paused)
                {
                    Thread.Sleep(100);
                }
                try
                {
                    if (Settings.Default.OnlyNew && !fileParser.IsFileNew(file))
                    {
                        totalLength += file.Length;
                        continue;
                    }
                    lastDirectory = ChangeCurrentDirectory(fileParser, ftpClient, lastDirectory, file, ref directories);
					//TotalUploadedBytes = totalLength;
                    if (!UploadFile(ftpClient, file))
						return;
                }
                finally
                {
                    OnPublishProcess(new ProcessDoneEventArgs((totalLength/(double) fileParser.TotalFilesLength)*100,
                                                              file.FullName.Substring(fileParser.SolutionFolder.Length)));
                }
            }
        }

    	private static string ChangeCurrentDirectory(PublishFileParser fileParser, FtpClient ftpClient, string lastDirectory,
    	                                             FileInfo file, ref List<string> direcories)
    	{
    		if (lastDirectory == null || lastDirectory != Path.GetDirectoryName(file.FullName))
    		{
    			for (int index = 0; index < direcories.Count; index++)
    			{
    				ftpClient.ChangeDir("..");
    			}
    			direcories = GetFileDirecories(fileParser, file.FullName);
    			foreach (var dir in direcories)
    			{
    				try
    				{
    					ftpClient.ChangeDir(dir);
    				}
    				catch (FtpClient.FtpException)
    				{
    					ftpClient.MakeDir(dir);
    					ftpClient.ChangeDir(dir);
    				}
    			}
    			lastDirectory = file.DirectoryName;
    		}
    		return lastDirectory;
    	}

    	private static bool UploadFile(FtpClient ftpClient, FileInfo file)
    	{
    		MessageBoxResult msgRes = MessageBoxResult.No;
			var retryNum = 0;
    		do
    		{
    			try
    			{
					if (msgRes == MessageBoxResult.Yes)
						ftpClient.Login();
    				ftpClient.Upload(file.FullName);
					return true;
    			}
    			catch (FtpClient.FtpException)
    			{
					if (retryNum++ < Settings.Default.RetryCountPerFile){
						msgRes = MessageBoxResult.Yes;
						continue;
					}
    				if (RetryConnection(file.FullName, out msgRes)) return false;
    			}
    		} while (msgRes == MessageBoxResult.Yes);
			return true;
    	}

    	private static bool RetryConnection(string file, out MessageBoxResult msgRes)
        {
            return (msgRes = MessageBox.Show(string.Format(CultureInfo.CurrentCulture, Resources.MessageBoxErrorUploadFile,
                                                           Path.GetFileName(file)), Resources.MessageBoxErrorCaption,
                                             MessageBoxButton.YesNoCancel, MessageBoxImage.Error)) == MessageBoxResult.Cancel;
        }

        private static List<string> GetFileDirecories(PublishFileParser fileParser, string file)
        {
            var fileInfo = new FileInfo(file);
            var dirs = new List<string>();
            var parentDir = fileInfo.Directory;
            while (parentDir != null && parentDir.FullName != fileParser.SolutionFolder  && parentDir.FullName.Length > fileParser.SolutionFolder.Length )
            {
                dirs.Insert(0, parentDir.Name);
                parentDir = parentDir.Parent;
            }
            return dirs;
        }

        private bool AskToUpdateFtpSettings(string message)
        {
            if (MessageBox.Show(Resources.SetupPublishFtpServer + (message != null ? "\r\nError:" + message : null), Resources.SetupPublishFtpServerCaption, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                OpenFtpSettings();
                return true;
            }
            return false;
        }

        public void OpenFtpSettings()
        {
            var process = Process.Start("notepad.exe", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PublishFileParser.PublishConfigFile));
            if (process != null)
                process.WaitForExit();            
        }

    }
}
